//    Copyright (C) Kherty.  All rights reserved.
#region
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Globalization;
using System.Reflection;
using System.Windows;
using OpenLS.Core.Notification;
using OpenLS.Drawing;
using OpenLS.Spreadsheet.Functions;
using OpenLS.Spreadsheet.Internals;
using OpenLS.Spreadsheet.Parsing;
using OpenLS.Spreadsheet.UI.Internals.Resources;

#endregion

namespace OpenLS.Spreadsheet.UI.Internals
{
    public class EditedCell : ViewModelBase
    {
        private readonly Cell _cell;
        private ParameterInfo _currentParameter;
        private Formula _formula;
        private const bool hasError = false;
        private EditionMode _mode;
        private bool _refusesSuggestions;
        private string _suggestion;
        private bool _wrapSuggested;
        private bool _committed;
        private List<object> _functionSuggestions;
        internal bool _wantsCapture;
        private WorksheetViewModel _worksheetViewModel;
        internal EditedCell(WorksheetViewModel worksheetViewModel,  Cell cell, string text)
            : this(worksheetViewModel, cell, text, EditionMode.Enter)
        {
        }
        public EditedCell(WorksheetViewModel worksheetViewModel, Cell cell, string text, EditionMode mode)
        {
            Contract.Requires(worksheetViewModel != null);
            Contract.Requires(cell != null);
            Contract.Assert(cell.CanBeEdited);
            _worksheetViewModel = worksheetViewModel;
            _cell = cell;
            if (text == null)
                text = "";
            text = text.Replace("\b", "");
            Text = text;
            Mode = mode;
        }
        public ParameterInfo CurrentParameter
        {
            get { return _currentParameter; }
            private set
            {
                _currentParameter = value;
                notifyPropertyChanged("CurrentParameter");
            }
        }
        public EditionMode Mode
        {
            get { return _mode; }
            set
            {
                _mode = value;
                notifyPropertyChanged("Mode");
            }
        }

        public Cell Cell
        {
            get { return _cell; }
        }
        public string SuggestionRemainingText
        {
            get
            {
                if (Suggestion == null)
                    return null;
                string s = Text;
                if (s.Length >= Suggestion.Length)
                    return null;
                return Suggestion.Substring(s.Length, Suggestion.Length - s.Length);
            }
        }
        public string Text
        {
            get
            {
                if (_formula == null)
                    return "";
                return _formula.Text;
            }
            set
            {
                _committed = false;
                var oldValue = Text;
                if (string.Compare(oldValue, value) == 0)
                    return;
                if (_formula == null || true)
                    Formula = new Formula(WorksheetViewModel, value);
                else
                    _formula.Parse(value);
                notifyPropertyChanged("Text");
                double dummy;
                if (DoubleUtil.TryParse(value, out dummy))
                    return;
                if (oldValue != null && oldValue.EndsWith(" "))
                {
                    Suggestion = null;
                    return;
                }

                string suggestionCandidate = null;
                foreach (var aCell in Cell.Worksheet.GetCells())
                {
                    if (aCell.Value is string && aCell != Cell )
                    {
                        var cellValue = (string) aCell.Value;
                        if (cellValue.StartsWith(value))
                        {
                            if (suggestionCandidate != null && suggestionCandidate != cellValue)
                            {
                                Suggestion = null;
                                return;
                            }
                            suggestionCandidate = cellValue;
                        }
                    }
                }
                Suggestion = suggestionCandidate;
            }
        }

        public bool RefusesSuggestions
        {
            get { return _refusesSuggestions; }
            set
            {
                _refusesSuggestions = value;
                if (value)
                    _suggestion = null;
                else
                {
                    throw new NotImplementedException();
                }
                notifyPropertyChanged("Suggestion");
                notifyPropertyChanged("Text");
            }
        }

        public string Suggestion
        {
            get { return _suggestion; }
            set
            {
                if (RefusesSuggestions)
                    return;
                if (_suggestion == value)
                    return;
                _suggestion = value;
                notifyPropertyChanged("Suggestion");
            }
        }

        internal Worksheet Worksheet
        {
            get { return _cell.Worksheet; }
        }

        internal bool HasError
        {
            get { return hasError; }
        }

        internal bool AcceptsPoint
        {
            get { 
                return 
                    (Mode == EditionMode.Enter && Formula.AcceptsPoint())
                    || Formula.AcceptsReference;
            } // && !CanCommit(); }
        }

        public Formula Formula
        {
            get
            {
                if (_formula == null)
                    Formula = new Formula(WorksheetViewModel, Text);
                return _formula;
            }
            private set
            {
                if (_formula == value)
                    return;
                if (_formula != null)
                    _formula.PropertyChanged -= formulaPropertyChanged;
                _formula = value;
                if (_formula != null)
                    _formula.PropertyChanged +=formulaPropertyChanged;
                notifyPropertyChanged("Formula");
                this._worksheetViewModel.Formula = value;
                calculateParameters();
                calculateFunctionSuggestions();
            }
        }

        void formulaPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            notifyPropertyChanged("Formula." + e.PropertyName);
        }

        public List<object> FunctionSuggestions
        {
            get { return _functionSuggestions; }
            private set
            {
                _functionSuggestions = value;
                notifyPropertyChanged("FunctionSuggestions");
            }
        }
        internal string FullCommitText
        {
            get
            {
                if (CanCommit(CommitText))
                    return CommitText;
                return CommitText +
                       ")";
            }
        }
        internal string CommitText
        {
            get
            {
                if (Suggestion != null)
                    return Suggestion;
                return Text;
            }
        }

        public WorksheetViewModel WorksheetViewModel
        {
            get
            {
                Contract.Ensures(Contract.Result<WorksheetViewModel>() != null);
                return _worksheetViewModel;
            }
        }

        private void calculateParameters()
        {
            if (_formula == null)
            {
                CurrentParameter = null;
                FunctionSuggestions = null;
                return;
            }
            if (_formula != null)
            {
                var tokens = new List<FormulaToken>(_formula.Tokens);
                var leftParenthesisCount = 0;
                var index = 0;
                tokens.Reverse();
                foreach (var formulaToken in tokens)
                {
                    var token = formulaToken as LiteralFormulaToken;
                    if (token == null)
                        continue;
                    if (token.IsLeftParenthesis)
                    {
                        leftParenthesisCount ++;
                        continue;
                    }
                    if (token.IsRightParenthesis)
                    {
                        leftParenthesisCount--;
                        continue;
                    }
                    if (token.IsComa)
                    {
                        if (leftParenthesisCount >= 0)
                            index++;
                        continue;
                    }
                    if (token.IsIdentifier)
                    {
                        if (leftParenthesisCount > 0)
                        {
                            var fn = FunctionManager.GetFunction(token.Text,
                                                                 Worksheet.Workbook.Document.Context) as AddInFunction;
                            var methodInfo = fn == null ? null : fn.Method;
                            if (methodInfo == null || methodInfo.GetParameters().Length == 0)
                                CurrentParameter = null;
                            else
                            {
                                var count = methodInfo.GetParameters().Length;
                                count = Math.Min(index, count - 1);
                                count = Math.Max(count, 0);
                                CurrentParameter = methodInfo.GetParameters()[count];
                            }
                            calculateFunctionSuggestions();
                            return;
                        }
                    }
                }
                CurrentParameter = null;
                calculateFunctionSuggestions();
            }
        }
        private void calculateFunctionSuggestions()
        {
            if (CurrentParameter != null)
            {
                FunctionSuggestions = null;
                return;
            }
            if (Formula == null)
            {
                FunctionSuggestions = null;
                return;
            }
            var tokens = Formula.Tokens;
            if (tokens == null || tokens.Count == 0 || tokens[0].Token.Type != TokenId.Equality)
            {
                FunctionSuggestions = null;
                return;
            }
            var token = tokens[tokens.Count - 1];
            var s = token.Text;
            var list = new List<object>();
            foreach (var fn in FunctionManager.GetFunctions(Worksheet.Workbook.Document.Context))
                if (fn.LocalizedName.StartsWith(s, true, CultureInfo.CurrentCulture))
                    list.Add(fn);
            list.Sort((o1, o2) =>
                          {
                              var fn1 = (AddInFunction) o1;
                              var fn2 = (AddInFunction) o2;
                              var name1 = (fn1.LocalizedName);
                              var name2 = (fn2.LocalizedName);
                              return name1.CompareTo(name2);
                          });
            FunctionSuggestions = list;
        }


        internal bool CanCommit()
        {
            return CanCommit(CommitText) || CanCommit(CommitText + ")");
        }
        internal bool CanCommitSimple()
        {
            return CanCommit(CommitText) ;
        }
        internal bool CanCommit(string text)
        {
            var expressionParser = new ExpressionParser(Worksheet, Cell, LocalizationMode.NonLocalized, CellReferenceMode.Dynamic);
            try
            {
                var exp = expressionParser.Expression(text);
                return true;
            }

            catch
            {
                return false;
            }
        }
        internal void Commit (string text)
        {
            Commit(text, false);
        }
        internal void Commit(string text, bool isArrayFormula)
        {
            if (_committed)
                return;
            _committed = true;
            text = text.Replace("\b", "");
            var oldFormula = _cell.Formula;
            var oldValue = _cell.Value;
            try
            {
                for (var i = 0; i < 20; i++) //20 is max ")"
                {
                    var ok = false;
                    try
                    {
                        if (isArrayFormula)
                        {
                            var selection = Worksheet.Selection;
                            //  using (Worksheet.Workbook.CreateUndoScope(UIStringTable.Formula))
                            {
                                selection.SetArrayFormula(text, true);
                            }
                        }
                        else
                        {
                            var uri = GetUri(text);
                            if (uri != null)
                            {
                                SetHyperLink(uri, text);
                            }
                            else
                                _cell.LocalizedFormula = text;
                        }
                        ok = true;
                    }
                    catch (DataValidationException dataValidationException)
                    {
                        string title = dataValidationException.DataValidation.PromptTitle;
                        if (string.IsNullOrEmpty(title))
                            title = UIStringTable.DataValidation;
                        string message = dataValidationException.DataValidation.Prompt;
                        if (string.IsNullOrEmpty(message))
                            message = "Invalid data validation";//\\loc\\
                        var result = MessageBox.Show (message, title, MessageBoxButton.OKCancel); 
                        switch (result)
                        {
                            case MessageBoxResult.OK:
                                throw;
                                case MessageBoxResult.Cancel:
                               return;
                            default:
                                throw;
                        }
                    }
                    catch (UnexpectedTokenException unexpectedTokenException)
                    {
                        if (unexpectedTokenException.Expected == TokenId.RightParenthesis)
                        {
                            text += ")";
                        }
                            // SJE 11-07
                        else return;
                        // throw;
                    }
                    if (ok)
                        break;
                }
                if (_wrapSuggested)
                {
                    _cell.TextWrapping = TextWrapping.Wrap;
                    _cell.Row.AutoFit();
                }
                //\\cell.SetFormula(text, true, true);
                //\\cell.Formula = text;
            }
            catch (CircularReferenceException exp)
            {
                MessageBox.Show(UIStringTable.CircularReference);
                _cell.Value = oldValue;
                _cell.Formula = oldFormula;
                throw new InvalidFormulaException( exp);
               //\\ throw new NotImplementedException(); //\\cell.Worksheet.EditedCell = null;
                //\\ cell.Worksheet.EditedCellTarget = null;
            }
        }

        private void SetHyperLink(Uri uri, string text)
        {
            using (_cell.Worksheet.Workbook.CreateUndoScope(StringTable.Hyperlink))
            {
                _cell.Value = text;
                _cell.FontUnderline = UnderlineType.Single;
                _cell.Foreground = new ThemeColor(ColorSchemeIndex.Hyperlink, 0);
                Hyperlink l = new Hyperlink();
                _cell.Hyperlink = l;
                l.ExternalUri = uri;
            }
        }

        private Uri GetUri(string text)
        {
            var s = text.ToLower();
            if (s.StartsWith("http://"))
            {
                Uri result;
                Uri.TryCreate(text, UriKind.Absolute, out result);
                return result;
            }
            if (s.StartsWith("www."))
            {
                Uri result;
                Uri.TryCreate("http://" + text, UriKind.Absolute, out result);
                return result;
            }
            return null;

        }

        internal void SuggestWrap()
        {
            _wrapSuggested = true;
        }

        internal void AcceptFunction(AddInFunction function)
        {
            var lastToken = Formula.Tokens[Formula.Tokens.Count - 1];
            var newToken = new LiteralFormulaToken(new Identifier(function.LocalizedName));
            Formula.Tokens.RemoveAt(Formula.Tokens.Count - 1);
            Formula.Tokens.Add(newToken);
            Formula.Tokens.Add(new LiteralFormulaToken(new LeftParenthesis()));
            notifyPropertyChanged("Text");
            var oldF = Formula;
            Formula = null;
            Formula = oldF;
            notifyPropertyChanged("Formula");
            Formula.FireRefresh(lastToken, newToken);
            calculateFunctionSuggestions();
            calculateFunctionSuggestions();
        }
        public void ToggleEditionMode()
        {
            switch (Mode)
            {
                case EditionMode.Edit:
                    Mode = EditionMode.Enter;
                    break;
                case EditionMode.Enter:
                    Mode = EditionMode.Edit;
                    break;
                default:
                    throw new NotSupportedException();
            }
        }

    }
}